//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.apache.hadoop.io.retry;

import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;

import com.starnet.yarnmonitor.yarn.handler.ApiException;
import lombok.extern.log4j.Log4j;
import org.apache.hadoop.classification.InterfaceAudience.Private;
import org.apache.hadoop.io.retry.CallReturn.State;
import org.apache.hadoop.io.retry.FailoverProxyProvider.ProxyInfo;
import org.apache.hadoop.io.retry.RetryPolicy.RetryAction;
import org.apache.hadoop.io.retry.RetryPolicy.RetryAction.RetryDecision;
import org.apache.hadoop.ipc.Client;
import org.apache.hadoop.ipc.ProtocolTranslator;
import org.apache.hadoop.ipc.RPC;
import org.apache.hadoop.ipc.RpcInvocationHandler;
import org.apache.hadoop.ipc.Client.ConnectionId;
import org.apache.hadoop.util.Time;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Log4j
@Private
public class RetryInvocationHandler<T> implements RpcInvocationHandler {
    public static final Logger LOG = LoggerFactory.getLogger(RetryInvocationHandler.class);
    private final RetryInvocationHandler.ProxyDescriptor<T> proxyDescriptor;
    private volatile boolean hasSuccessfulCall;
    private final RetryPolicy defaultPolicy;
    private final Map<String, RetryPolicy> methodNameToPolicyMap;
    private final AsyncCallHandler asyncCallHandler;

    protected RetryInvocationHandler(FailoverProxyProvider<T> proxyProvider, RetryPolicy retryPolicy) {
        this(proxyProvider, retryPolicy, Collections.emptyMap());
    }

    protected RetryInvocationHandler(FailoverProxyProvider<T> proxyProvider, RetryPolicy defaultPolicy, Map<String, RetryPolicy> methodNameToPolicyMap) {
        this.hasSuccessfulCall = false;
        this.asyncCallHandler = new AsyncCallHandler();
        this.proxyDescriptor = new RetryInvocationHandler.ProxyDescriptor(proxyProvider);
        this.defaultPolicy = defaultPolicy;
        this.methodNameToPolicyMap = methodNameToPolicyMap;
    }

    private RetryPolicy getRetryPolicy(Method method) {
        RetryPolicy policy = (RetryPolicy)this.methodNameToPolicyMap.get(method.getName());
        return policy != null ? policy : this.defaultPolicy;
    }

    private long getFailoverCount() {
        return this.proxyDescriptor.getFailoverCount();
    }

    private RetryInvocationHandler.Call newCall(Method method, Object[] args, boolean isRpc, int callId) {
        return (RetryInvocationHandler.Call)(Client.isAsynchronousMode() ? this.asyncCallHandler.newAsyncCall(method, args, isRpc, callId, this) : new RetryInvocationHandler.Call(method, args, isRpc, callId, this));
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        boolean isRpc = isRpcInvocation(this.proxyDescriptor.getProxy());
        int callId = isRpc ? Client.nextCallId() : -2;
        RetryInvocationHandler.Call call = this.newCall(method, args, isRpc, callId);

        CallReturn c;
//寄

            c = call.invokeOnce();
            State state = c.getState();
            if (state == State.ASYNC_INVOKED) {
                return null;
            }

        if (c.getState() == State.RETRY){
            log.error("连接Yarn失败");
            throw new ApiException("yarn宕机");
        }

        return c.getReturnValue();
    }

    private RetryInvocationHandler.RetryInfo handleException(Method method, int callId, RetryPolicy policy, RetryInvocationHandler.Counters counters, long expectFailoverCount, Exception e) throws Exception {
        RetryInvocationHandler.RetryInfo retryInfo = RetryInvocationHandler.RetryInfo.newRetryInfo(policy, e, counters, this.proxyDescriptor.idempotentOrAtMostOnce(method), expectFailoverCount);
        if (retryInfo.isFail()) {
            if (retryInfo.action.reason != null && LOG.isDebugEnabled()) {
                LOG.debug("Exception while invoking call #" + callId + " " + this.proxyDescriptor.getProxyInfo().getString(method.getName()) + ". Not retrying because " + retryInfo.action.reason, e);
            }

            throw retryInfo.getFailException();
        } else {
            this.log(method, retryInfo.isFailover(), counters.failovers, retryInfo.delay, e);
            return retryInfo;
        }
    }

    private void log(Method method, boolean isFailover, int failovers, long delay, Exception ex) {
        boolean info = this.hasSuccessfulCall || failovers != 0 || this.asyncCallHandler.hasSuccessfulCall();
        if (info || LOG.isDebugEnabled()) {
            StringBuilder b = (new StringBuilder()).append(ex + ", while invoking ").append(this.proxyDescriptor.getProxyInfo().getString(method.getName()));
            if (failovers > 0) {
                b.append(" after ").append(failovers).append(" failover attempts");
            }

            b.append(isFailover ? ". Trying to failover " : ". Retrying ");
            b.append(delay > 0L ? "after sleeping for " + delay + "ms." : "immediately.");
            if (info) {
                LOG.info(b.toString());
            } else {
                LOG.debug(b.toString(), ex);
            }

        }
    }

    protected Object invokeMethod(Method method, Object[] args) throws Throwable {
        try {
            if (!method.isAccessible()) {
                method.setAccessible(true);
            }

            Object r = method.invoke(this.proxyDescriptor.getProxy(), args);
            this.hasSuccessfulCall = true;
            return r;
        } catch (InvocationTargetException var4) {
            throw var4.getCause();
        }
    }

    @VisibleForTesting
    static boolean isRpcInvocation(Object proxy) {
        if (proxy instanceof ProtocolTranslator) {
            proxy = ((ProtocolTranslator)proxy).getUnderlyingProxyObject();
        }

        if (!Proxy.isProxyClass(proxy.getClass())) {
            return false;
        } else {
            InvocationHandler ih = Proxy.getInvocationHandler(proxy);
            return ih instanceof RpcInvocationHandler;
        }
    }

    public void close() throws IOException {
        this.proxyDescriptor.close();
    }

    public ConnectionId getConnectionId() {
        return RPC.getConnectionIdForProxy(this.proxyDescriptor.getProxy());
    }

    @VisibleForTesting
    public FailoverProxyProvider<T> getProxyProvider() {
        return this.proxyDescriptor.fpp;
    }

    private static class RetryInfo {
        private final long retryTime;
        private final long delay;
        private final RetryAction action;
        private final long expectedFailoverCount;
        private final Exception failException;

        RetryInfo(long delay, RetryAction action, long expectedFailoverCount, Exception failException) {
            this.delay = delay;
            this.retryTime = Time.monotonicNow() + delay;
            this.action = action;
            this.expectedFailoverCount = expectedFailoverCount;
            this.failException = failException;
        }

        boolean isFailover() {
            return this.action != null && this.action.action == RetryDecision.FAILOVER_AND_RETRY;
        }

        boolean isFail() {
            return this.action != null && this.action.action == RetryDecision.FAIL;
        }

        Exception getFailException() {
            return this.failException;
        }

        static RetryInvocationHandler.RetryInfo newRetryInfo(RetryPolicy policy, Exception e, RetryInvocationHandler.Counters counters, boolean idempotentOrAtMostOnce, long expectedFailoverCount) throws Exception {
            RetryAction max = null;
            long maxRetryDelay = 0L;
            Exception ex = null;
            Iterable<Exception> exceptions = e instanceof MultiException ? ((MultiException)e).getExceptions().values() : Collections.singletonList(e);
            Iterator var11 = ((Iterable)exceptions).iterator();

            while(true) {
                Exception exception;
                RetryAction a;
                do {
                    if (!var11.hasNext()) {
                        return new RetryInvocationHandler.RetryInfo(maxRetryDelay, max, expectedFailoverCount, ex);
                    }

                    exception = (Exception)var11.next();
                    a = policy.shouldRetry(exception, counters.retries, counters.failovers, idempotentOrAtMostOnce);
                    if (a.action != RetryDecision.FAIL && a.delayMillis > maxRetryDelay) {
                        maxRetryDelay = a.delayMillis;
                    }
                } while(max != null && max.action.compareTo(a.action) >= 0);

                max = a;
                if (a.action == RetryDecision.FAIL) {
                    ex = exception;
                }
            }
        }

        public String toString() {
            return "RetryInfo{retryTime=" + this.retryTime + ", delay=" + this.delay + ", action=" + this.action + ", expectedFailoverCount=" + this.expectedFailoverCount + ", failException=" + this.failException + '}';
        }
    }

    private static class ProxyDescriptor<T> {
        private final FailoverProxyProvider<T> fpp;
        private long failoverCount = 0L;
        private ProxyInfo<T> proxyInfo;

        ProxyDescriptor(FailoverProxyProvider<T> fpp) {
            this.fpp = fpp;
            this.proxyInfo = fpp.getProxy();
        }

        synchronized ProxyInfo<T> getProxyInfo() {
            return this.proxyInfo;
        }

        synchronized T getProxy() {
            return this.proxyInfo.proxy;
        }

        synchronized long getFailoverCount() {
            return this.failoverCount;
        }

        synchronized void failover(long expectedFailoverCount, Method method, int callId) {
            if (this.failoverCount == expectedFailoverCount) {
                this.fpp.performFailover(this.proxyInfo.proxy);
                ++this.failoverCount;
            } else {
                RetryInvocationHandler.LOG.warn("A failover has occurred since the start of call #" + callId + " " + this.proxyInfo.getString(method.getName()));
            }

            this.proxyInfo = this.fpp.getProxy();
        }

        boolean idempotentOrAtMostOnce(Method method) throws NoSuchMethodException {
            Method m = this.fpp.getInterface().getMethod(method.getName(), method.getParameterTypes());
            return m.isAnnotationPresent(Idempotent.class) || m.isAnnotationPresent(AtMostOnce.class);
        }

        void close() throws IOException {
            this.fpp.close();
        }
    }

    static class Counters {
        private int retries;
        private int failovers;

        Counters() {
        }

        boolean isZeros() {
            return this.retries == 0 && this.failovers == 0;
        }
    }

    static class Call {
        private final Method method;
        private final Object[] args;
        private final boolean isRpc;
        private final int callId;
        private final RetryInvocationHandler.Counters counters = new RetryInvocationHandler.Counters();
        private final RetryPolicy retryPolicy;
        private final RetryInvocationHandler<?> retryInvocationHandler;
        private RetryInvocationHandler.RetryInfo retryInfo;

        Call(Method method, Object[] args, boolean isRpc, int callId, RetryInvocationHandler<?> retryInvocationHandler) {
            this.method = method;
            this.args = args;
            this.isRpc = isRpc;
            this.callId = callId;
            this.retryPolicy = retryInvocationHandler.getRetryPolicy(method);
            this.retryInvocationHandler = retryInvocationHandler;
        }

        int getCallId() {
            return this.callId;
        }

        RetryInvocationHandler.Counters getCounters() {
            return this.counters;
        }

        synchronized Long getWaitTime(long now) {
            return this.retryInfo == null ? null : this.retryInfo.retryTime - now;
        }

        synchronized CallReturn invokeOnce() {
            try {
                if (this.retryInfo != null) {
                    return this.processWaitTimeAndRetryInfo();
                } else {
                    long failoverCount = this.retryInvocationHandler.getFailoverCount();

                    try {
                        return this.invoke();
                    } catch (Exception var4) {
                        if (RetryInvocationHandler.LOG.isTraceEnabled()) {
                            RetryInvocationHandler.LOG.trace(this.toString(), var4);
                        }

                        if (Thread.currentThread().isInterrupted()) {
                            throw var4;
                        } else {
                            this.retryInfo = this.retryInvocationHandler.handleException(this.method, this.callId, this.retryPolicy, this.counters, failoverCount, var4);
                            return this.processWaitTimeAndRetryInfo();
                        }
                    }
                }
            } catch (Throwable var5) {
                return new CallReturn(var5);
            }
        }

        CallReturn processWaitTimeAndRetryInfo() throws InterruptedIOException {
            Long waitTime = this.getWaitTime(Time.monotonicNow());
            RetryInvocationHandler.LOG.trace("#{} processRetryInfo: retryInfo={}, waitTime={}", new Object[]{this.callId, this.retryInfo, waitTime});
            if (waitTime != null && waitTime > 0L) {
                try {
                    Thread.sleep(this.retryInfo.delay);
                } catch (InterruptedException var4) {
                    Thread.currentThread().interrupt();
                    if (RetryInvocationHandler.LOG.isDebugEnabled()) {
                        RetryInvocationHandler.LOG.debug("Interrupted while waiting to retry", var4);
                    }

                    InterruptedIOException intIOE = new InterruptedIOException("Retry interrupted");
                    intIOE.initCause(var4);
                    throw intIOE;
                }
            }

            this.processRetryInfo();
            return CallReturn.RETRY;
        }

        synchronized void processRetryInfo() {
            this.counters.retries++;
            if (this.retryInfo.isFailover()) {
                this.retryInvocationHandler.proxyDescriptor.failover(this.retryInfo.expectedFailoverCount, this.method, this.callId);
                this.counters.failovers++;
            }

            this.retryInfo = null;
        }

        CallReturn invoke() throws Throwable {
            return new CallReturn(this.invokeMethod());
        }

        Object invokeMethod() throws Throwable {
            if (this.isRpc) {
                Client.setCallIdAndRetryCount(this.callId, this.counters.retries, this.retryInvocationHandler.asyncCallHandler);
            }

            return this.retryInvocationHandler.invokeMethod(this.method, this.args);
        }

        public String toString() {
            return this.getClass().getSimpleName() + "#" + this.callId + ": " + this.method.getDeclaringClass().getSimpleName() + "." + this.method.getName() + "(" + (this.args != null && this.args.length != 0 ? Arrays.toString(this.args) : "") + ")";
        }
    }
}
